package com.ease.util {

   import flash.utils.getQualifiedClassName;

   import mx.collections.XMLListCollection;

   /**
    * An utility class to capture common constants and methods used in
    * <code>MxmlSerializer</code> and <code>MxmlDeserializer</code>
    * @see MxmlSerializer
    * @see MxmlDeserializer
    */
   public class MxmlUtil {

      private static const CHAR_SPACE:String = " ";

      private static const PERSISTABLE_TAG:String = "Persistable";

      /**
       * As recommended here http://msdn.microsoft.com/en-us/library/35577sxd.aspx
       * encode characters as _xHHHH_, where HHHH stands for the four-digit
       * hexadecimal UCS-2 code for the character in the most significant bit first order.
       */
      private static const CHAR_SPACE_ESCAPED:String = "_x0020_";

      /**
       * the space character
       */
      private static var REGEX_ESCAPE:RegExp = new RegExp(CHAR_SPACE, "g");

      /**
       * the escaped character.
       */
      private static var REGEX_UNESCAPE:RegExp = new RegExp(CHAR_SPACE_ESCAPED, "g");

      /**
       * The attribute which the <code>MxmlSerializer</code> puts for each unique object
       * in oder to be able to serialize more than one references to it.
       */
      public static const ATTRIB_ID:String = "mxmlObjId";

      /**
       * The attribute which the <code>MxmlSerializer</code> puts for each object
       * while serializing it if it turns out to be a reference to already serialized
       * object.
       */
      public static const ATTRIB_REF:String = "mxmlRefId";

      /**
       * The attribute which the <code>MxmlSerializer</code> puts for each object
       * while serializing to mark the reference to the metadata containing object
       * loading information.
       */
      public static const ATTRIB_INFO_REF:String = "mxmlRefInfo";

      /**
       * Cache for properties saved per class and filter
       */
      private static var _propertiesCache:MruCache = new MruCache(200);

      /**
       * The constant we use to mark objects were <code>null</code> when saved.
       */
      public static const NULL:String = "{null}";

      /**
       * Serialize the persistable properties, i.e the ones marked with the
       * [Persistable] tag.
       * <p/>
       * Binary = 00000000 00000000 00000000 00000001
       */
      public static const PROPS_PERSISTABLE:uint = 1;

      /**
       * Serialize the GUI related properties (x, y, width, height, etc ..)
       * The exact list of properties can be found in the <code>MxmlManifest</code>
       * class.
       * <p/>
       * Binary = 00000000 00000000 00000000 00000010
       */
      public static const PROPS_GUI:uint = 1 << 1;

      /**
       * Serialize all public properties (no matter whether they are
       * marked as persistable or not) which have both: read and write access.
       * Note that accessors (set/get) are also treated as properties.
       * <p/>
       * Binary = 00000000 00000000 00000000 00000100
       */
      public static const PROPS_ALL:uint = 1 << 2;

      /**
       * Whether a type is simple or not. In the <code>MxmlSerializer</code>
       * we call simple those types which we serialize directly via
       * the <code>SimpleXMLEncoder</code> and deserialize via the
       * <code>SimpleXMLDecoder</code> classes. These classes represent objects
       * as simple XML nodes, without preserving the type information. That is why we
       * use them at the last stage of the object save, when we have reached the
       * simplest possible types for serialization.
       */
      public static function isSimpleType(type:String):Boolean {
         switch (type) {
            case "String":
            case "Boolean":
            case "Number":
            case "int":
            case "uint":
            case "Class":
            case "XML":
            case "undefined":
            case "null":
               return true;
         }

         return false;
      }

      /**
       * Whether the supplied <code>type<code> is a global Flex type different from
       * the simple and container types.
       *
       * <p/> <code>Date</code> is one example of such type.
       *
       * @see #isSimpleType()
       * @see #isContainerType()
       */
      public static function isGlobalType(type:String):Boolean {
         switch (type) {
            case "Date":
               return true;
         }

         return false;
      }

      /**
       * A convenience method which checks whether a XML node represents a null
       * value (in the sense of MxmlSerializer and MxmlDeserializer processing).
       */
      public static function isNullNode(node:XML):Boolean {
         if (node.hasSimpleContent() && (node.toString() == NULL)) {
            return true;
         }
         return false;
      }

      /**
       * We need some special handling of the containers, cause these do not
       * have their children as properties. So we need to do special traversing
       * for these types in order to get and serialize the children. Therefore we need
       * to distinguish them.
       */
      public static function isContainerType(type:String):Boolean {
         switch(type) {
            case "Object":
            case "Array":
            case "flash.utils.Dictionary":
               return true;
         }
         return false;
      }

      /**
       * Whether or not the passed attribute name matches one of the attributes
       * the <code>MxmlSerializer</code> uses in order to store its data.
       */
      public static function isReservedAttribute(attributName:String):Boolean {
         switch(attributName) {
            case ATTRIB_ID:
            case ATTRIB_REF:
            case ATTRIB_INFO_REF:
               return true;
         }
         return false;
      }

      [ArrayElementType("String")]
      /**
       * Rettrieves the list of properties found in the type definition of the object.
       * @param object
       *    The object for which you need the property names of the serializable
       *    properties.
       * @param propFilter
       *    A filter denoting which properties the user wants to get back.
       * @return An array with the property names which match the <code>propFilter</code>
       * @see #PROP_ALL
       * @see #PROP_PERSISTABLE
       * @see #PROP_GUI
       */
      public static function getProperties(object:Object,
                                           propFilter:uint):Array {
         var cacheKey:String = getQualifiedClassName(object) + "-" + propFilter.toString();

         // Cache lookup - this has a huge speed gain
         var properties:Array = _propertiesCache.getValue(cacheKey) as Array;
         if (properties != null) {
            return properties;
         }

         properties = [];
         if (propFilter & PROPS_GUI) {
            var classNames:Array = ReflectionUtil.getTypes(object);
            for each (className in classNames) {
               var className:String = className.replace("::",".");
               var inclusions:XMLListCollection = MxmlManifest.getInclusions(className);
               for each (var inclusion:String in inclusions) {
                  properties.push(inclusion);
               }
            }
         }

         var members:Array = getOrderedProperties(object, propFilter);
         for each(var member:String in members){
            if (properties.indexOf(member) == -1) {
               properties.push(member);
            }
         }

         // Cache the return value
         _propertiesCache.add(cacheKey, properties);

         return properties;
      }

      /**
       * An utility method which returns the properties of an object
       * in the correct order and properly filtered.
       */
      private static function getOrderedProperties(object:Object,
                                                   propFilter:uint):Array /*String*/ {
         var result:Array = [];

         if ((propFilter & PROPS_ALL) > 0) {
            // Return all the properties.
            result = ReflectionUtil.getPropertyNames(
               object, ReflectionUtil.ACCESS_READWRITE, PERSISTABLE_TAG);

            // Since the mask contains "all properties" there is no need of further
            // processing.
            return result;
         }


         if ((propFilter & PROPS_PERSISTABLE) > 0) {
            result = getOrderedPersistableProperties(object);
            return result;
         }

         return result;
      }

      [ArrayElementType("String")]
      /**
       * Return the list of [Persistable] properties found in the type
       * definition of an object
       */
      private static function getOrderedPersistableProperties(object:Object):Array /*String*/ {
         var members:Array = [];
         var isClassPersistable:Boolean =
            ReflectionUtil.hasUninheritedClassMetadata(object, PERSISTABLE_TAG);

         if (isClassPersistable) {
            // We need only the properties and the readwrite accessors.
            members = ReflectionUtil.getPropertyNames(
               object, ReflectionUtil.ACCESS_READWRITE, PERSISTABLE_TAG);
         } else {
            members = ReflectionUtil.
               getTaggedPropertyNames(object, PERSISTABLE_TAG, PERSISTABLE_TAG);
         }

         return members;
      }


      /**
       * Escape unsuitable characters from a string that will be used as node name in
       * XML document.
       * <p/> The escape format for a character <code>c</code> is _xHHHH_, where HHHH
       * stands for the four-digit hexadecimal UCS-2 code for the character in the
       * most significant bit first order
       *
       * <p/>With this method, you can have the following example if you suspect that
       * the XML node name can have unsuitable characters:
       *
       * <listing version="3.0">
       *  var xmlNode:XML=<new/>
       *  xmlNode.setName(MxmlUtil.escape(nodeName));
       * </listing>
       *
       * @see #unescape()
       */
      public static function escape(name:String):String {
         return name.replace(REGEX_ESCAPE, CHAR_SPACE_ESCAPED);
      }

      /**
       * Escape back a string coming from XML node name into a full fledged Flex string.
       * This is the opposite of the <code>escape()</code> method.
       *
       * <p/> Example:
       *
       * <listing version="3.0">
       *  var xmlNode:XML= ...
       *  var name:String = MxmlUtil.unescape(xmlNode.name());
       * </listing>
       *
       * @see #escape()
       */
      public static function unescape(name:String):String {
         return name.replace(REGEX_UNESCAPE, CHAR_SPACE);
      }

   } // class
} // package
